package utils

import (
	"fmt"
	"time"
)

type ValMonitorItem struct {
	Catalog string
	Key     string
	Val     interface{}
	UpTime  time.Time
	Cb      *ValMonitorItemCallBack
}

type ValMonitor struct {
	ID                       string
	rec                      chan *ValMonitorItem
	addBeforeOutputEventChan chan *ValMonitorEvent
	delBeforeOutputEventChan chan *ValMonitorEvent
	rtAddChan                chan *ValMonitorItemCallBack
	rtDelChan                chan *ValMonitorItemCallBack
	rtGetChan                chan *ValMonitorItem
	intervalAddChan          chan *ValMonitorItemCallBack
	intervalDelChan          chan *ValMonitorItemCallBack
	valMap                   map[string]*ValMonitorItem
	ticker                   *time.Ticker
	intervalListener         *SafeList
	rtListener               *SafeList
	beforeOutputListener     *SafeList
}

type ValMonitorItemCallBack func(val *ValMonitorItem)
type ValMonitorEvent func(sender *ValMonitor)

func NewValMonitor(id string, cacheNum int, interval time.Duration) *ValMonitor {
	rval := &ValMonitor{
		ID:                       id,
		rec:                      make(chan *ValMonitorItem, cacheNum),
		addBeforeOutputEventChan: make(chan *ValMonitorEvent),
		delBeforeOutputEventChan: make(chan *ValMonitorEvent),
		rtAddChan:                make(chan *ValMonitorItemCallBack),
		rtDelChan:                make(chan *ValMonitorItemCallBack),
		rtGetChan:                make(chan *ValMonitorItem),
		intervalAddChan:          make(chan *ValMonitorItemCallBack),
		intervalDelChan:          make(chan *ValMonitorItemCallBack),
		valMap:                   make(map[string]*ValMonitorItem),
		ticker:                   time.NewTicker(interval),
		intervalListener:         NewSafeList(),
		rtListener:               NewSafeList(),
		beforeOutputListener:     NewSafeList(),
	}
	return rval
}

func (this *ValMonitor) AddBeforeOutputListener(cb *ValMonitorEvent) {
	this.addBeforeOutputEventChan <- cb
}

func (this *ValMonitor) RemoveBeforeOutputListener(cb *ValMonitorEvent) {
	this.delBeforeOutputEventChan <- cb
}

func (this *ValMonitor) AddIntervalCallBack(cb *ValMonitorItemCallBack) {
	this.intervalAddChan <- cb
}

func (this *ValMonitor) AddRtCallBack(cb *ValMonitorItemCallBack) {
	this.rtAddChan <- cb
}

func (this *ValMonitor) RemoveIntervalCallBack(cb *ValMonitorItemCallBack) {
	this.intervalDelChan <- cb
}

func (this *ValMonitor) RemoveRtCallBack(cb *ValMonitorItemCallBack) {
	this.rtDelChan <- cb
}

func (this *ValMonitor) Stop() {
	this.rec <- nil
}

func ValCanCompare(val interface{}) {

}

func (this *ValMonitor) Start() {
	task := func() {
	whilefor:
		for {
			select {
			case <-this.ticker.C:
				this.doOutput()
			case cb, ok := <-this.addBeforeOutputEventChan:
				if !ok {
					break whilefor
				}
				this.beforeOutputListener.Add(cb)
			case cb, ok := <-this.delBeforeOutputEventChan:
				if !ok {
					break whilefor
				}
				this.beforeOutputListener.Remove(cb)
			case cb, ok := <-this.rtAddChan:
				if !ok {
					break whilefor
				}
				this.rtListener.Add(cb)
			case rec, ok := <-this.rtGetChan:
				if !ok {
					break whilefor
				}

				mapKey := fmt.Sprintf("%s-%s", rec.Catalog, rec.Key)
				itm := this.valMap[mapKey]
				if itm != nil {
					rec.Val = itm.Val
					rec.UpTime = time.Now()
				}
				if rec.Cb != nil {
					cb := *rec.Cb
					cb(rec)
				}
			case cb, ok := <-this.rtDelChan:
				if !ok {
					break whilefor
				}
				this.rtListener.Remove(cb)
			case cb, ok := <-this.intervalAddChan:
				if !ok {
					break whilefor
				}
				this.intervalListener.Add(cb)
			case cb, ok := <-this.intervalDelChan:
				if !ok {
					break whilefor
				}
				this.intervalListener.Remove(cb)
			case rec, ok := <-this.rec:
				if !ok {
					break whilefor
				}
				if rec == nil {
					break whilefor
				}
				mapKey := fmt.Sprintf("%s-%s", rec.Catalog, rec.Key)
				if rec.UpTime.IsZero() {
					delete(this.valMap, mapKey)
				} else {
					itm := this.valMap[mapKey]
					up := true
					if itm == nil {
						this.valMap[mapKey] = rec
					} else {
						if itm.Val != rec.Val {
							itm.Val = rec.Val
							itm.UpTime = rec.UpTime
						} else {
							up = false
						}
					}

					if up {
						this.rtListener.ForEach(func(val interface{}) {
							funPtr := val.(*ValMonitorItemCallBack)
							cb := *funPtr
							cb(rec)
						})
					}
				}
			}
		}

		Log.Infof("%s 退出", this.ID)
	}
	GoFunc2(task)
}

func (this *ValMonitor) doOutput() {
	this.beforeOutputListener.ForEach(func(val interface{}) {
		funPtr := val.(*ValMonitorEvent)
		cb := *funPtr
		cb(this)
	})

	for _, v := range this.valMap {
		this.intervalListener.ForEach(func(val interface{}) {
			funPtr := val.(*ValMonitorItemCallBack)
			cb := *funPtr
			cb(v)
		})
	}
}

func (this *ValMonitor) GetVal(catalog, key string, cb *ValMonitorItemCallBack) {
	itm := &ValMonitorItem{
		Catalog: catalog,
		Key:     key,
		Cb:      cb,
	}
	this.rtGetChan <- itm
}

func (this *ValMonitor) DelVal(catalog, key string) {
	itm := &ValMonitorItem{
		Catalog: catalog,
		Key:     key,
	}
	this.rec <- itm
}

func (this *ValMonitor) SetVal(catalog, key string, val interface{}) {
	itm := &ValMonitorItem{
		Catalog: catalog,
		Key:     key,
		Val:     val,
		UpTime:  time.Now(),
	}
	this.rec <- itm
}
